package com.freewater.cargotracker.infrastructure.persistence.mybatis.repository;

import com.freewater.cargotracker.domain.model.handlingevent.HandlingEvent;
import com.freewater.cargotracker.domain.model.handlingevent.HandlingEventRepository;
import com.freewater.cargotracker.domain.model.handlingevent.HandlingHistory;
import com.freewater.cargotracker.infrastructure.persistence.mybatis.converter.HandlingEventConverter;
import com.freewater.cargotracker.infrastructure.persistence.mybatis.dbobject.CargoPO;
import com.freewater.cargotracker.infrastructure.persistence.mybatis.dbobject.HandlingEventPO;
import com.freewater.cargotracker.infrastructure.persistence.mybatis.dbobject.LocationPO;
import com.freewater.cargotracker.infrastructure.persistence.mybatis.dbobject.VoyagePO;
import com.freewater.cargotracker.infrastructure.persistence.mybatis.mapper.*;
import org.springframework.stereotype.Repository;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;

@Repository
public class HandlingEventRepositoryImpl implements HandlingEventRepository {

    private final HandlingEventMapper handlingEventMapper;
    private final CargoMapper cargoMapper;
    private final CargoHelperMapper cargoHelperMapper;
    private final VoyageMapper voyageMapper;
    private final VoyageHelperMapper voyageHelperMapper;
    private final LocationMapper locationMapper;

    public HandlingEventRepositoryImpl(HandlingEventMapper handlingEventMapper,
                                       CargoMapper cargoMapper,
                                       CargoHelperMapper cargoHelperMapper,
                                       VoyageMapper voyageMapper,
                                       VoyageHelperMapper voyageHelperMapper,
                                       LocationMapper locationMapper) {
        this.handlingEventMapper = handlingEventMapper;
        this.cargoMapper = cargoMapper;
        this.cargoHelperMapper = cargoHelperMapper;
        this.voyageMapper = voyageMapper;
        this.voyageHelperMapper = voyageHelperMapper;
        this.locationMapper = locationMapper;
    }

    @Override
    public void save(HandlingEvent handlingEvent) {
        final HandlingEventPO handlingEventPO = HandlingEventConverter.serialize(handlingEvent);
        handlingEventPO.setVersion(1);
        this.handlingEventMapper.insert(handlingEventPO);
    }

    @Override
    public HandlingHistory findHandlingHistoryOfCargo(Long cargoId) {
        final List<HandlingEventPO> handlingEventPOs = this.handlingEventMapper.findByCargoId(cargoId);
        if (handlingEventPOs.isEmpty()) {
            return HandlingHistory.EMPTY;
        }
        final CargoPO cargoPO = this.cargoMapper.findById(cargoId);
        this.cargoHelperMapper.findCargoInfo(cargoPO);
        handlingEventPOs.forEach(eventPO -> eventPO.setCargoPO(cargoPO));
        //
        final Set<Long> voyageIds = handlingEventPOs.stream()
                .filter(s -> s.getVoyagePO() != null && s.getVoyagePO().getId() != null)
                .map(s -> s.getVoyagePO().getId())
                .collect(Collectors.toSet());
        final Map<Long, VoyagePO> voyagePOMap = new HashMap<>();
        if (!voyageIds.isEmpty()) {
            for (Long voyageId : voyageIds) {
                final VoyagePO voyagePO = this.voyageMapper.findById(voyageId);
                this.voyageHelperMapper.findVoyageInfo(voyagePO);
                voyagePOMap.put(voyageId, voyagePO);
            }
        }
        handlingEventPOs.forEach(eventPO -> {
            final VoyagePO voyagePO = eventPO.getVoyagePO();
            if (voyagePO != null && voyagePO.getId() != null) {
                final Long voyageId = voyagePO.getId();
                eventPO.setVoyagePO(voyagePOMap.get(voyageId));
            }
        });
        //
        final List<LocationPO> locationPOs = handlingEventPOs.stream().map(s -> s.getLocation()).collect(Collectors.toList());
        final Set<Long> locationIds = extractLocationId(locationPOs);
        List<LocationPO> locationPOList = new ArrayList<>();
        if (!locationIds.isEmpty()) {
            locationPOList = locationMapper.findByIds(locationIds);
        }
        final Map<Long, LocationPO> locationMap = locationPOList.stream()
                .collect(Collectors.toMap(s -> s.getId(), s -> s));
        handlingEventPOs.forEach(eventPO -> fillLocationPO(eventPO::getLocation, eventPO::setLocation, locationMap));
        //
        final List<HandlingEvent> handlingEvents = handlingEventPOs.stream()
                .map(HandlingEventConverter::deserialize).collect(Collectors.toList());
        return new HandlingHistory(handlingEvents);
    }

    private Set<Long> extractLocationId(List<LocationPO> locationPOs) {
        return locationPOs.stream()
                .filter(s -> s != null).map(s -> s.getId()).collect(Collectors.toSet());
    }

    private void fillLocationPO(Supplier<LocationPO> locationIdSupplier,
                                Consumer<LocationPO> consumer,
                                Map<Long, LocationPO> locationPOMap) {
        final LocationPO po = locationIdSupplier.get();
        if (po == null) {
            return;
        }
        final Long locationId = po.getId();
        final LocationPO locationPO = locationPOMap.get(locationId);
        consumer.accept(locationPO);
    }
}
